using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO; //Filesystem access
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32; //Registry access

namespace PloxEdApp
{
    public partial class PloxEd : Form
    {
        int lastIndex = -1;
        bool deletePending = false;

        List<string> ploxDescs = new List<string>();
        List<string> ploxRegexes = new List<string>(); //Changed from List<Regex>. Plox will clean up a bad regex for me.
        List<Uri> ploxUris = new List<Uri>();
        List<ushort> ploxExpNew = new List<ushort>(); //How old files need to be in days until they expire
        List<ushort> ploxExpMod = new List<ushort>(); //How long files need to be unchanged in days until they expire
        List<byte> ploxRecurseDepth = new List<byte>(); //Is this job recursive?
        List<bool> ploxDelEmpty = new List<bool>(); //If so, do we remove empty subdirectories?
        List<bool> ploxInsensitive = new List<bool>(); //Is the job case insensitive?

        //Defaults for the globals. These are the same in Plox.
        public double timerInterval; //In minutes. This is a double because of Timer, but I'll only accept non-negative integers.
        public string logFile = Environment.SystemDirectory + Path.DirectorySeparatorChar + "ploxlog.txt";
        public bool haltOnFileDelFail = false;
        public bool haltOnDirDelFail = false;
        public bool logOnFTPError = true;
        public bool redactFTPCredentials = true;
        public bool logOnLocalDelFail = true;
        public bool logOnLocalEnumFail = true;
        public bool logToEventLog = true;
        public bool logToFlatFile = true;

        public PloxEd()
        {
            InitializeComponent();
        }

        private void PloxEd_Load(object sender, EventArgs e)
        {
            //I don't know why, but if you do this, when you disable the control it does not 
            //grey out. I don't really like this, but I change the color of pathBox for user
            //feedback and everything should remain consistent.

            this.descBox.BackColor = Color.FromKnownColor(KnownColor.Window);
            this.uriBox.BackColor = Color.FromKnownColor(KnownColor.Window);
            this.maskBox.BackColor = Color.FromKnownColor(KnownColor.Window);
            this.newUpDown.BackColor = Color.FromKnownColor(KnownColor.Window);
            this.modUpDown.BackColor = Color.FromKnownColor(KnownColor.Window);

            //Here I usually catch Exception rather than the specific problem...
            //either I use a default value or I totally fail. 

            try //In case the current user has suck credentials
            {
                RegistryKey regSvcs = Registry.LocalMachine.OpenSubKey("SOFTWARE");
            }
            catch (Exception x) //I am not discriminating - this is probably a security exception, but you never know.
            {
                MessageBox.Show("Plox could not access the registry. The error returned was"
                                    + '\n' + '\n' + x.Message, "Oh no!"); //Whoops, verboten
                this.Dispose(); //Japanese honorable suicide
            }

            ReadReg();
            RefreshJobs(); //Load stuff into jobListBox

            //Disable most of the controls to prevent user confusion before first select.

            UiEnable(false);

        } //End of PloxEd_Load

        private void ReadReg()
        {
            try //Method-spanning try since the registry could be damaged or unreadable
            {
                RegistryKey ploxReg = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Plox"); //Cannot fail due to security

                //At this point, we assume that ploxReg is not null and is functional. If there is
                //a NullReferenceException (or any other exception), PloxEd will gracefully terminate.

                //Get timer

                uint tempUint;

                //Not sure if a single if would work - it's legal code, but I think it might break in optimization.
                if (uint.TryParse(ploxReg.GetValue("timer", "30").ToString(), out tempUint))
                    if (tempUint > 0) 
                        this.timerInterval = tempUint;
                    else
                        this.timerInterval = 30;

                //Get global strings

                this.logFile = ploxReg.GetValue("logfile", this.logFile).ToString();

                //Get global bools

                bool tempBool; //No need to init

                if (bool.TryParse(ploxReg.GetValue("haltonfiledelfail", this.haltOnFileDelFail).ToString(), out tempBool))
                    this.haltOnFileDelFail = tempBool;

                if (bool.TryParse(ploxReg.GetValue("haltondirdelfail", this.haltOnDirDelFail).ToString(), out tempBool))
                    this.haltOnDirDelFail = tempBool;

                if (bool.TryParse(ploxReg.GetValue("logonftperror", this.logOnFTPError).ToString(), out tempBool))
                    this.logOnFTPError = tempBool;

                if (bool.TryParse(ploxReg.GetValue("redactftpcredentials", this.redactFTPCredentials).ToString(), out tempBool))
                    this.redactFTPCredentials = tempBool;

                if (bool.TryParse(ploxReg.GetValue("logonlocaldelfail", this.logOnLocalDelFail).ToString(), out tempBool))
                    this.logOnLocalDelFail = tempBool;

                if (bool.TryParse(ploxReg.GetValue("logonlocalenumfail", this.logOnLocalEnumFail).ToString(), out tempBool))
                    this.logOnLocalEnumFail = tempBool;

                if (bool.TryParse(ploxReg.GetValue("logtoeventlog", this.logToEventLog).ToString(), out tempBool))
                    this.logToEventLog = tempBool;

                if (bool.TryParse(ploxReg.GetValue("logtoflatfile", this.logToFlatFile).ToString(), out tempBool))
                    this.logToFlatFile = tempBool;

                //Get jobs

                if (ploxReg.SubKeyCount != 0) //No entries - this is not a bad thing.
                {
                    String[] keyNames = ploxReg.GetSubKeyNames();

                    foreach (String thisKeyName in keyNames) //RegistryKey is sealed, so I can't extend it with this code.
                    {
                        RegistryKey thisReg = ploxReg.OpenSubKey(thisKeyName); //Get one key
                        String[] thisVals = thisReg.GetValueNames(); //Get the values

                        //I can't think of an instance in which a value can contain null - but even if that
                        //happens, the exception will be caught and an error will be written to the log.

                        //Reinit these for each iteration of the foreach
                        string thisUri = ""; //Will URIify later
                        string thisMask = "";
                        ushort thisExpNew = 0; //If either one of these values (but not both) is not present,
                        ushort thisExpMod = 0; //then it is 0, which we treat as "never expires".
                        byte thisRecurseDepth = 0;
                        bool thisDelEmpty = false;
                        bool thisInsensitive = true;

                        for (int i = 0; i < thisVals.Length; i++)
                        {
                            switch (thisVals[i].ToLowerInvariant())
                            {
                                case "uri":
                                    thisUri = Uri.UnescapeDataString(thisReg.GetValue(thisVals[i]).ToString());
                                    break;
                                case "mask":
                                    thisMask = thisReg.GetValue(thisVals[i]).ToString();
                                    break;
                                case "expnew":
                                    if (!ushort.TryParse(thisReg.GetValue(thisVals[i]).ToString(), out thisExpNew))
                                        thisExpMod = ushort.MaxValue;
                                    break;
                                case "expmod":
                                    if (!ushort.TryParse(thisReg.GetValue(thisVals[i]).ToString(), out thisExpMod))
                                        thisExpMod = ushort.MaxValue;
                                    break;
                                case "recursedepth":
                                    if (!byte.TryParse(thisVals[i], out thisRecurseDepth))
                                        thisRecurseDepth = 0;
                                    break;
                                case "delempty":
                                    if (!bool.TryParse(thisVals[i], out thisDelEmpty))
                                        thisDelEmpty = false;
                                    break;
                                case "caseinsensitive":
                                    if (!bool.TryParse(thisVals[i], out thisInsensitive))
                                        thisInsensitive = true;
                                    break;
                            }
                        } //Moved to here. if the if-then below is in the for, things get double-counted. Try it and see...

                        if (Uri.IsWellFormedUriString(Uri.EscapeUriString(thisUri), UriKind.Absolute) && !thisMask.Equals("") 
                            || thisExpNew.Equals(ushort.MaxValue) || thisExpMod.Equals(ushort.MaxValue))
                        {
                            //Unlike Plox, we allow invalid URIs - BUT - we notify the user by 
                            //setting the background of pathTextBox Tomatato when the URI is not
                            //valid. There is an action connected to uriBox that does this.

                            this.ploxDescs.Add(thisKeyName);
                            this.ploxUris.Add(new Uri(thisUri, UriKind.Absolute));
                            this.ploxRegexes.Add(thisMask);
                            this.ploxExpNew.Add(thisExpNew);
                            this.ploxExpMod.Add(thisExpMod);
                            this.ploxRecurseDepth.Add(thisRecurseDepth);
                            this.ploxDelEmpty.Add(thisDelEmpty);
                            this.ploxInsensitive.Add(thisInsensitive);

                        } //End of if-then (Executes when correctly named but possibly invalid values exist)
                    } //End of foreach (Iterates once per key)
                } //End of if-else (Sanity checking - does the registry contain valid data for Plox?)

                //Drops to here when there are no subkeys

            } //End of try
            catch (Exception x) //Again, a general purpose catch. Probably a hardware error at this point.
            {
                MessageBox.Show("PloxEd could not read from the registry. The error returned by Windows was:"
                                    + '\n' + '\n' + x.Message, "Oh no!");
                this.Dispose(); //Japanese honorable suicide
            }
        }

        private bool ValidateUri() //Privides a bool as well as user feedback.
        {
            if (!this.uriBox.Enabled)
            {
                this.uriBox.BackColor = SystemColors.Window;
                return Uri.IsWellFormedUriString(this.uriBox.Text, UriKind.Absolute);
            }

            if (Uri.IsWellFormedUriString(Uri.EscapeUriString(this.uriBox.Text), UriKind.Absolute))
            {
                Uri testUri = new Uri(this.uriBox.Text);

                if (testUri.Scheme.Equals("ftp") && !testUri.Authority.Equals(""))
                {
                    this.uriBox.BackColor = SystemColors.Window;
                    return true;
                }

                if (testUri.Scheme.Equals("file") && testUri.Authority.Equals("")
                   && !testUri.LocalPath.Equals(""))
                {
                    try //Argh. MS should provide a Path.IsWellFormed method.
                    {
                        Path.GetFullPath(testUri.LocalPath);
                    }
                    catch
                    {
                        this.uriBox.BackColor = Color.Tomato;
                        return false;
                    }

                    this.uriBox.BackColor = SystemColors.Window;
                    return true;
                }
            }

            this.uriBox.BackColor = Color.Tomato;
            return false;
        }

        private void RefreshJobs()
        {
            string currentSelection = this.jobListBox.Text; //Returns "" for no selection.

            this.jobListBox.Items.Clear(); //This also deselects everything.
            this.jobListBox.Items.AddRange(this.ploxDescs.ToArray());

            if (!currentSelection.Equals("") && this.jobListBox.Items.Contains(currentSelection)) //Restore the current selection, if applicable.
                this.jobListBox.SelectedItem = currentSelection; //Causes an jobListBox_SelectedIndexChanged to fire.

            this.jobListBox.Refresh();
        }

        private void UiEnable(bool status) //Enable or disable all controls at once.
        {
            this.descBox.Enabled = status;
            this.uriBox.Enabled = status;
            this.maskBox.Enabled = status;
            this.browseButton.Enabled = status;
            this.newUpDown.Enabled = status;
            this.modUpDown.Enabled = status;
            this.delTaskButton.Enabled = status;
            this.matchCaseBox.Enabled = status;
            this.recurseUpDown.Enabled = status;
            this.delEmptyBox.Enabled = status;

            return;
        }

        private void jobListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Write out the current job and load the new job into the UI.

            if (this.jobListBox.SelectedIndex.Equals(-1)) //Nothing is selected.
                return;

            UiEnable(true); //In case this is an initial select.

            if (!this.deletePending) //Stop the flush, but still refresh the UI.
            {
                TryFlush(this.lastIndex);
                this.deletePending = false;
            }

            int index = this.ploxDescs.IndexOf(this.jobListBox.Text);

            this.descBox.Text = this.ploxDescs[index];
            this.uriBox.Text = this.ploxUris[index].ToString();
            this.maskBox.Text = this.ploxRegexes[index];
            this.newUpDown.Value = this.ploxExpNew[index];
            this.modUpDown.Value = this.ploxExpMod[index];
            this.recurseUpDown.Value = this.ploxRecurseDepth[index];
            this.delEmptyBox.Checked = this.ploxDelEmpty[index];
            this.matchCaseBox.Checked = !this.ploxInsensitive[index];

            this.lastIndex = index;
        }

        private bool InvariantMatch(String x, String y)
        {
            if (x.ToLowerInvariant().Equals(y.ToLowerInvariant())) //FOO = foo = Foo
                return true;
            return false;
        }

        private void TryFlush(int index)
        {
            if (this.jobListBox.SelectedIndex.Equals(-1) || index.Equals(-1)
                || index >= this.ploxDescs.Count)
                return;

            int prevIndex = this.jobListBox.SelectedIndex;

            this.ploxDescs[index] = this.descBox.Text;

            if(ValidateUri()) //Check for a bogus URI.
                this.ploxUris[index] = new Uri(this.uriBox.Text);
            
            this.ploxRegexes[index] = this.maskBox.Text;
            this.ploxExpNew[index] = (ushort)this.newUpDown.Value;
            this.ploxExpMod[index] = (ushort)this.modUpDown.Value;
            this.ploxRecurseDepth[index] = (byte)this.recurseUpDown.Value;
            this.ploxDelEmpty[index] = this.delEmptyBox.Checked;
            this.ploxInsensitive[index] = !this.matchCaseBox.Checked;

            //Disconnect the event handler to prevent problems.
            this.jobListBox.SelectedIndexChanged -= new System.EventHandler(this.jobListBox_SelectedIndexChanged);

            RefreshJobs();
            this.jobListBox.SelectedIndex = prevIndex;

            //Restore it after the index update.
            this.jobListBox.SelectedIndexChanged += new System.EventHandler(this.jobListBox_SelectedIndexChanged);
        }

        private void commitButton_Click(object sender, EventArgs e)
        {
            if (!lastIndex.Equals(int.MinValue)) //Nothing to flush yet.
                TryFlush(lastIndex);
            WriteReg(); //Do it!
        }
        
        private void WriteReg()
        {
            try
            {
                RegistryKey ploxReg = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Plox", true); //Open for writing

                foreach (String thisKey in ploxReg.GetSubKeyNames()) //Erase everything - clean slate
                    ploxReg.DeleteSubKeyTree(thisKey);

                foreach (String thisValue in ploxReg.GetValueNames())
                    ploxReg.DeleteValue(thisValue);

                //Write globals

                ploxReg.SetValue("Timer", this.timerInterval, RegistryValueKind.DWord);
                ploxReg.SetValue("LogFile", this.logFile, RegistryValueKind.String);
                ploxReg.SetValue("HaltOnFileDelFail", this.haltOnFileDelFail, RegistryValueKind.String);
                ploxReg.SetValue("HaltOnDirDelFail", this.haltOnDirDelFail, RegistryValueKind.String);
                ploxReg.SetValue("LogOnFTPError", this.logOnFTPError, RegistryValueKind.String);
                ploxReg.SetValue("RedactFTPCredentials", this.redactFTPCredentials, RegistryValueKind.String);
                ploxReg.SetValue("LogOnLocalDelFail", this.logOnLocalDelFail, RegistryValueKind.String);
                ploxReg.SetValue("LogOnLocalEnumFail", this.logOnLocalEnumFail, RegistryValueKind.String);
                ploxReg.SetValue("LogToEventLog", this.logToEventLog, RegistryValueKind.String);
                ploxReg.SetValue("LogToFlatFile", this.logToFlatFile, RegistryValueKind.String);

                //Write jobs

                for (int i = 0; i < this.ploxDescs.Count; i++)
                {
                    ploxReg.CreateSubKey(this.ploxDescs[i]);
                    RegistryKey thisSubKey = ploxReg.OpenSubKey(this.ploxDescs[i], true);

                    thisSubKey.SetValue("Uri", Uri.EscapeUriString(this.ploxUris[i].ToString()));
                    thisSubKey.SetValue("Mask", this.ploxRegexes[i]);
                    thisSubKey.SetValue("ExpNew", this.ploxExpNew[i]);
                    thisSubKey.SetValue("ExpMod", this.ploxExpMod[i]);
                    thisSubKey.SetValue("RecurseDepth", this.ploxRecurseDepth[i]);
                    thisSubKey.SetValue("DelEmpty", this.ploxDelEmpty[i]);
                    thisSubKey.SetValue("CaseInsensitive", this.ploxInsensitive[i]);
                }

                ploxReg.Flush(); //Physically write it.
            }
            catch (Exception x)
            {
                MessageBox.Show("PloxEd could not write to the registry; some settings may be lost. The error returned by Windows was:"
                    + '\n' + '\n' + x.Message, "Oh no!");
            }
        }

        private void newTaskButton_Click(object sender, EventArgs e)
        {
            int taskNum = 1; //Viewed by humans, so not zero-indexed

            while (ploxDescs.Contains("New Task #" + taskNum.ToString()))
                taskNum++; //Increment and retry

            this.ploxDescs.Add("New Task #" + taskNum.ToString());
            this.ploxUris.Add(new Uri("file:///")); //Used to pop a dialog, but not necessary anymore.
            this.ploxRegexes.Add("*.*");
            this.ploxExpNew.Add(0); //Never - prevents havoc in that the user
            this.ploxExpMod.Add(0); //MUST set these for Plox to take any action.
            this.ploxRecurseDepth.Add(0);
            this.ploxDelEmpty.Add(false);
            this.ploxInsensitive.Add(true);

            RefreshJobs();

            //Select the new entry
            this.jobListBox.SelectedIndex = this.jobListBox.Items.IndexOf("New Task #" + taskNum.ToString());
        }

        private void browseButton_Click(object sender, EventArgs e)
        {
            this.uriBox.Text = browsePop();
        }

        private String browsePop()
        {
            FolderBrowserDialog modalFolder = new FolderBrowserDialog();
            modalFolder.RootFolder = Environment.SpecialFolder.Desktop;
            modalFolder.ShowDialog(this); //I own the window

            return new Uri(modalFolder.SelectedPath, UriKind.Absolute).ToString();
        }

        private void delTaskButton_Click(object sender, EventArgs e)
        {
            if (this.jobListBox.SelectedIndex.Equals(-1)) //Is something even selected?
                return;

            this.deletePending = true; //Stops the next flush from nuking out the newly-selected item.

            //Get the index of the current selection.

            int currentIndex = this.jobListBox.Items.IndexOf(this.jobListBox.Text);

            this.ploxDescs.RemoveAt(currentIndex);
            this.ploxUris.RemoveAt(currentIndex);
            this.ploxRegexes.RemoveAt(currentIndex);
            this.ploxExpNew.RemoveAt(currentIndex);
            this.ploxExpMod.RemoveAt(currentIndex);
            this.ploxInsensitive.RemoveAt(currentIndex);
            this.ploxRecurseDepth.RemoveAt(currentIndex);
            this.ploxDelEmpty.RemoveAt(currentIndex);

            RefreshJobs();

            //Reset everything or TryFlush breaks.

            this.maskBox.BackColor = Color.FromKnownColor(KnownColor.Window);

            UiEnable(false);

            this.descBox.Text = "";
            this.uriBox.Text = ""; //The control is disabled, so it doesn't Tomato when I do this.
            this.maskBox.Text = "";
            this.newUpDown.Value = 0;
            this.modUpDown.Value = 0;

            if (!this.jobListBox.Items.Count.Equals(0)) //It wasn't the last item.
            {
                if (currentIndex.Equals(0)) //Can't select -1...
                    this.jobListBox.SelectedIndex = 0;
                else
                    this.jobListBox.SelectedIndex = currentIndex - 1;
            }
        }

        private void UriBox_Validate(object sender, EventArgs e)
        {
            ValidateUri(); //Discard the event args and pass control
        }

        private void globalSettingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PloxSet mySettings = new PloxSet(this); //I pass this instance of PloxEd to the child window so it can get and set the timer.
            mySettings.ShowDialog(this);

            this.Refresh();
        }

        private void ploxStatusToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PloxStatus myStatus = new PloxStatus();
            myStatus.ShowDialog(this);

            this.Refresh();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PloxAbout myAbout = new PloxAbout();
            myAbout.ShowDialog(this);

            this.Refresh();
        }
    }
}